/*   Carina
 *   Copyright (C) 2009 2010 2011  Zdravko Velinov
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "entity-inspector.hh"

#include "carina/ces-loader.hh"

#include <QFileDialog>
#include <QMessageBox>
#include <QMenu>

EntityInspector::EntityInspector(CESFile& ces_file, const WorldManagerPtr& wmgr, const ResourceManagerPtr& res_mgr)
    :   m_EntModel(ces_file.getEntitySection(), ces_file.getGeometrySection(), wmgr, res_mgr),
        m_GeomModel(ces_file.getGeometrySection(), res_mgr),
        m_CESFile(ces_file),
        m_WorldMgr(wmgr),
        m_ResMgr(res_mgr)
{
    m_EntInsp.setupUi(this);

    m_EntInsp.GeometryListView->setModel(&m_GeomModel);
    m_EntInsp.GeometryListView->setContextMenuPolicy(Qt::CustomContextMenu);

    m_EntInsp.EntityListView->setModel(&m_EntModel);
    m_EntInsp.EntityListView->setContextMenuPolicy(Qt::CustomContextMenu);
}

bool EntityInspector::reload()
{
    return m_GeomModel.reload() && m_EntModel.reload();
}

void EntityInspector::clear()
{
    m_GeomModel.clear();
    m_EntModel.clear();
}

void EntityInspector::on_AddEntityButton_clicked(bool checked)
{
    CreateModelDialog dlg(m_CESFile);
    if(dlg.exec() != QDialog::Accepted)
        return;
    m_EntModel.insertRow(dlg.getName().toStdString(),
                         dlg.getCollisionHierarchyInfo().toStdString(),
                         dlg.getMeshName().toStdString());
}

void EntityInspector::on_DeleteEntityButton_clicked(bool checked)
{
    QStringList not_deleted;
    QItemSelectionModel* selection_model = m_EntInsp.EntityListView->selectionModel();
    QModelIndexList midx_list = selection_model->selectedRows();
    QList<QPersistentModelIndex> pmidx_list;
    for(QModelIndexList::const_iterator i = midx_list.begin(); i != midx_list.end(); ++i)
        pmidx_list.append(QPersistentModelIndex(*i));
    CESEntitySection& ent_section = m_CESFile.getEntitySection();
    for(QList<QPersistentModelIndex>::iterator i = pmidx_list.begin(); i != pmidx_list.end(); ++i)
    {
        string name;
        CESEntity& ent = ent_section.getEntity(i->row());
        ent.getAttribute("Name", name);
        ModelPtr model = m_WorldMgr->getModel(name);
        size_t use_count = model.use_count();
        if(use_count > 2)
        {
            not_deleted.push_back(name.c_str());
            continue;
        }
        m_EntModel.removeRow(i->row());
    }
    if(!not_deleted.isEmpty())
    {
        QMessageBox::warning(this, "Warning", "The following entities have not been deleted because they are still in usage: " + not_deleted.join(", "));
        return;
    }
}

void EntityInspector::on_AddMeshButton_clicked(bool checked)
{
    QString filename(QFileDialog::getOpenFileName(this, tr("Add Mesh"), QString(), "Mesh (*.cem)"));
    if(filename.isEmpty())
        return;
    string fstr = m_ResMgr->getResourceRelative(filename.toStdString());
    if(fstr.empty())
    {
        QMessageBox::critical(this, "Error", "The selected file path is not part of any resource paths");
        return;
    }
    m_GeomModel.insertRow(fstr);
}

void EntityInspector::on_RemoveMeshButton_clicked(bool checked)
{
    QStringList not_deleted;
    QItemSelectionModel* selection_model = m_EntInsp.GeometryListView->selectionModel();
    QModelIndexList midx_list = selection_model->selectedRows();
    QList<QPersistentModelIndex> pmidx_list;
    for(QModelIndexList::const_iterator i = midx_list.begin(); i != midx_list.end(); ++i)
        pmidx_list.append(QPersistentModelIndex(*i));
    CESGeometrySection& geom_section = m_CESFile.getGeometrySection();
    for(QList<QPersistentModelIndex>::iterator i = pmidx_list.begin(); i != pmidx_list.end(); ++i)
    {
        CESMeshReference& mesh_ref = geom_section.getMeshReference(i->row());
        string name = mesh_ref.getMeshName();
        MeshPtr mesh = m_ResMgr->getMesh(name);
        if(m_ResMgr->getMeshExternalRefCount(mesh) != 1)
        {
            not_deleted.push_back(name.c_str());
            continue;
        }
        m_GeomModel.removeRow(i->row());
    }
    if(!not_deleted.isEmpty())
    {
        QMessageBox::warning(this, "Warning", "The following meshes have not been deleted because they are still in usage: " + not_deleted.join(", "));
        return;
    }
}

void EntityInspector::on_EntityListView_customContextMenuRequested(const QPoint& pos)
{
    QMenu menu(this);
    QAction *add_ent = menu.addAction("Add Entity"),
            *del_ent = menu.addAction("Delete Entity"),
            *inst_ent = nullptr;

    QModelIndex index(m_EntInsp.EntityListView->indexAt(pos));
    if(index.isValid())
    {
        menu.addSeparator();
        inst_ent = menu.addAction("Create Instance");
    }
    else
        del_ent->setEnabled(false);

    QAction* result = menu.exec(QCursor::pos());
    if(result == add_ent)
        on_AddEntityButton_clicked();
    else if(result == del_ent)
    {
        string name;
        CESEntity& ent = m_CESFile.getEntitySection().getEntity(index.row());
        ent.getAttribute("Name", name);
        ModelPtr model = m_WorldMgr->getModel(name);
        size_t use_count = model.use_count();
        if(use_count > 2)
            QMessageBox::warning(this, "Warning", "The following entity has not been deleted because it is still in usage: " + QString(name.c_str()));
        else
            m_EntModel.removeRow(index.row());
    }
    else if(index.isValid() && result == inst_ent)
    {
        string ent_name;
        CESEntity& ent = m_CESFile.getEntitySection().getEntity(index.row());
        ent.getAttribute("Name", ent_name);
        emit createModelInstance(ent_name);
    }
}

void EntityInspector::on_GeometryListView_customContextMenuRequested(const QPoint& pos)
{
    QMenu menu(this);
    bool in_place;
    QAction *add_mesh = menu.addAction("Add Mesh"),
            *rem_mesh = menu.addAction("Remove Mesh"),
            *conv_mesh = nullptr,
            *ent_mesh = nullptr;

    QModelIndex index(m_EntInsp.GeometryListView->indexAt(pos));
    if(index.isValid())
    {
        menu.addSeparator();
        in_place = m_CESFile.getGeometrySection().getMeshReference(index.row()).getMeshInfo()->isInPlace();
        ent_mesh = menu.addAction("Add Entity");
        conv_mesh = menu.addAction(in_place ? "Export to file" : "Import in place");
    }
    else
        rem_mesh->setEnabled(false);

    QAction* result = menu.exec(QCursor::pos());
    if(result == add_mesh)
        on_AddMeshButton_clicked();
    else if(result == rem_mesh)
    {
        CESMeshReference& mesh_ref = m_CESFile.getGeometrySection().getMeshReference(index.row());
        string name = mesh_ref.getMeshName();
        MeshPtr mesh = m_ResMgr->getMesh(name);
        if(m_ResMgr->getMeshExternalRefCount(mesh) != 1)
            QMessageBox::warning(this, "Warning", "The following mesh has not been deleted because it is still in usage: " + QString(name.c_str()));
        else
            m_GeomModel.removeRow(index.row());
    }
    else if(index.isValid())
    {
        CESMeshReference& mref = m_CESFile.getGeometrySection().getMeshReference(index.row());
        if(result == conv_mesh)
        {
            if(in_place)
            {
                CEMFile cem_file;
                QString filename = QFileDialog::getSaveFileName(this, "Export Mesh", QString(), "Mesh (*.cem)");
                if(filename.isEmpty())
                    return;
                string fstr = m_ResMgr->getResourceRelative(filename.toStdString());
                if(fstr.empty())
                {
                    QMessageBox::critical(this, "Error", "The selected file path is not part of any resource paths");
                    return;
                }
                cem_file.getMesh() = static_cast<CESMeshInPlace&>(*mref.getMeshInfo()).getMesh();
                if(!cem_file.save(filename.toStdString()))
                {
                    QMessageBox::critical(this, "Error", "The application has failed to export the following mesh: " + QString(mref.getMeshName().c_str()));
                    return;
                }
                m_ResMgr->registerMesh(fstr, m_ResMgr->getMesh(mref.getMeshName()));
                mref.setMeshInfo(make_aligned_shared<CESMeshFromFile>(fstr));
            }
            else
            {
                CEMFile cem_file;
                string filename = static_cast<CESMeshFromFile&>(*mref.getMeshInfo()).getFileName();
                if(!cem_file.open(m_ResMgr->getResource(filename)))
                {
                    QMessageBox::critical(this, "Error", "The application has failed to import the following mesh: " + QString(mref.getMeshName().c_str()));
                    return;
                }
                m_ResMgr->unregisterMesh(filename, false);
                CESMeshInPlacePtr mesh_ref = make_aligned_shared<CESMeshInPlace>();
                mesh_ref->getMesh() = cem_file.getMesh();
                mref.setMeshInfo(mesh_ref);
            }
        }
        else if(result == ent_mesh)
        {
            CreateModelDialog dlg(m_CESFile);
            if(dlg.exec() != QDialog::Accepted)
                return;
            dlg.setMeshName(mref.getMeshName().c_str());
            m_EntModel.insertRow(dlg.getName().toStdString(),
                                 dlg.getCollisionHierarchyInfo().toStdString(),
                                 dlg.getMeshName().toStdString());
        }
    }
}

GeometryListModel::GeometryListModel(CESGeometrySection& geom_section, const ResourceManagerPtr& res_mgr)
    :   m_GeomSection(geom_section),
        m_ResMgr(*res_mgr)
{
}

int GeometryListModel::rowCount(const QModelIndex& parent) const
{
    return m_GeomSection.getMeshRefCount();
}

int GeometryListModel::columnCount(const QModelIndex& parent) const
{
    return ATTR_COUNT;
}

QVariant GeometryListModel::data(const QModelIndex& index, int role) const
{
    if(!index.isValid() || index.row() >= (int)m_GeomSection.getMeshRefCount())
        return QVariant();
    if(role == Qt::DisplayRole)
        switch(index.column())
        {
        case MESH_NAME: return m_GeomSection.getMeshReference(index.row()).getMeshName().c_str();
        case MESH_IN_PLACE: return m_GeomSection.getMeshReference(index.row()).getMeshInfo()->isInPlace() ? "in place" : "file";
        }
    return QVariant();
}

QVariant GeometryListModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if(role == Qt::DisplayRole)
        switch(section)
        {
        case MESH_NAME: return "Name";
        case MESH_IN_PLACE: return "Type";
        }
    return QVariant();
}

bool GeometryListModel::insertRows(int position, int rows, const QModelIndex& parent)
{
    return false;
}

bool GeometryListModel::insertRow(const string& name)
{
    int row_idx = m_GeomSection.getMeshRefCount();
    beginRemoveRows(QModelIndex(), row_idx, row_idx);
    m_GeomSection.addMeshReference(name, make_aligned_shared<CESMeshFromFile>(name));
    endRemoveRows();
    return true;
}

bool GeometryListModel::removeRows(int row, int count, const QModelIndex& parent)
{
    int row_end = row + count;
    beginRemoveRows(parent, row, row_end-1);
    for(int i = row; i < row_end; ++i)
    {
        CESMeshReference& mesh_ref = m_GeomSection.getMeshReference(i);
        m_GeomSection.removeMeshReference(i);
        m_ResMgr.unregisterMesh(mesh_ref.getMeshName());
    }
    endRemoveRows();
    return true;
}

bool GeometryListModel::reload()
{
    beginResetModel();
    m_ResMgr.clear();
    bool res = CESLoader::loadGeometry(m_GeomSection, m_ResMgr);
    endResetModel();
    return res;
}

void GeometryListModel::clear()
{
    beginResetModel();
    m_GeomSection.clear();
    m_ResMgr.clear();
    endResetModel();
}

EntityListModel::EntityListModel(CESEntitySection& ent_section, CESGeometrySection& geom_section, const WorldManagerPtr& wmgr, const ResourceManagerPtr& res_mgr)
    :   m_EntSection(ent_section),
        m_GeomSection(geom_section),
        m_WorldMgr(wmgr),
        m_ResMgr(*res_mgr)
{
}

int EntityListModel::rowCount(const QModelIndex& parent) const
{
    return m_EntSection.getEntityCount();
}

int EntityListModel::columnCount(const QModelIndex& parent) const
{
    return ATTR_COUNT;
}

QVariant EntityListModel::data(const QModelIndex& index, int role) const
{
    if(!index.isValid() || index.row() >= (int)m_EntSection.getEntityCount())
        return QVariant();
    if(role == Qt::DisplayRole)
        switch(index.column())
        {
        case ENTITY_NAME:
        {
            string str;
            m_EntSection.getEntity(index.row()).getAttribute("Name", str);
            return str.c_str();
        }
        case ENTITY_COL_HIERARCHY:
        {
            string str;
            m_EntSection.getEntity(index.row()).getAttribute("Collision hierarchy", str);
            return str.c_str();
        }
        case ENTITY_MESH_NAME:
        {
            string str;
            m_EntSection.getEntity(index.row()).getAttribute("Rendering mesh", str);
            return str.c_str();
        }
        }
    return QVariant();
}

QVariant EntityListModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if(role == Qt::DisplayRole)
        switch(section)
        {
        case ENTITY_NAME: return "Name";
        case ENTITY_COL_HIERARCHY: return "BVH";
        case ENTITY_MESH_NAME: return "Mesh";
        }
    return QVariant();
}

bool EntityListModel::insertRows(int row, int count, const QModelIndex& parent)
{
    return false;
}

bool EntityListModel::removeRows(int row, int count, const QModelIndex& parent)
{
    bool result = false;
    int row_end = row + count;
    beginRemoveRows(parent, row, row_end-1);
    for(int i = row; i < row_end; ++i)
    {
        string name;
        CESEntity& ent = m_EntSection.getEntity(i);
        ent.getAttribute("Name", name);
        m_EntSection.removeEntity(i);
        m_WorldMgr->unregisterModel(name);
        result = true;
    }
    endRemoveRows();
    return result;
}

bool EntityListModel::insertRow(const string& name, const string& col, const string& mesh_name)
{
    int row_idx = m_EntSection.getEntityCount();
    beginRemoveRows(QModelIndex(), row_idx, row_idx);
    CESEntity& ent = m_EntSection.addEntity();
    ent.setAttribute("Rendering mesh", mesh_name);
    ent.setAttribute("Name", name);
    ent.setAttribute("Collision hierarchy", QString(col.c_str()).toLower().toStdString());
    ModelPtr model(CESLoader::loadModel(ent, m_GeomSection, m_ResMgr));
    assert(model.get());
    m_WorldMgr->registerModel(name, model);
    endRemoveRows();
    return true;
}

bool EntityListModel::reload()
{
    beginResetModel();
    m_WorldMgr->clear();
    bool res = CESLoader::loadEntities(m_EntSection, m_GeomSection, m_ResMgr, m_WorldMgr);
    endResetModel();
    return res;
}

void EntityListModel::clear()
{
    beginResetModel();
    m_EntSection.clear();
    m_WorldMgr->clear();
    endResetModel();
}

CreateModelDialog::CreateModelDialog(CESFile& ces_file)
    :   m_CESFile(ces_file)
{
    m_Dialog.setupUi(this);

    CESGeometrySection& ces_geom = ces_file.getGeometrySection();
    for(size_t i = 0; i < ces_geom.getMeshRefCount(); ++i)
        m_Dialog.MeshComboBox->insertItem(i, ces_geom.getMeshReference(i).getMeshName().c_str());
}

QString CreateModelDialog::getName() const
{
    return m_Dialog.NameLineEdit->text();
}

QString CreateModelDialog::getCollisionHierarchyInfo() const
{
    return m_Dialog.ColHComboBox->currentText();
}

void CreateModelDialog::setMeshName(const QString& mesh_name)
{
    m_Dialog.MeshComboBox->setCurrentIndex(m_Dialog.MeshComboBox->findText(mesh_name));
}

QString CreateModelDialog::getMeshName() const
{
    return m_Dialog.MeshComboBox->currentText();
}

void CreateModelDialog::accept()
{
    QString name = m_Dialog.NameLineEdit->text();
    if(name.isEmpty() || m_CESFile.getEntitySection().getEntity(name.toStdString()))
    {
        QMessageBox::critical(this, tr("Error"), tr("The name must be unique."));
        return;
    }

    QString mesh_name = m_Dialog.MeshComboBox->currentText();
    if(mesh_name.isEmpty())
    {
        QMessageBox::critical(this, tr("Error"), tr("No mesh is selected."));
        return;
    }

    QDialog::accept();
}